/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
 * Other names may be trademarks of their respective owners.]
 *
 * ------------------
 * SWTGraphics2D.java
 * ------------------
 * (C) Copyright 2006-2008, by Henry Proudhon and Contributors.
 *
 * Original Author:  Henry Proudhon (henry.proudhon AT ensmp.fr);
 * Contributor(s):   Cedric Chabanois (cchabanois AT no-log.org);
 *                   David Gilbert (for Object Refinery Limited);
 *                   Ronnie Duan (see bug report 2583891);
 *
 * Changes
 * -------
 * 14-Jun-2006 : New class (HP);
 * 29-Jan-2007 : Fixed the fillRect method (HP);
 * 31-Jan-2007 : Moved the dummy JPanel to SWTUtils.java,
 *               implemented the drawLine method (HP);
 * 07-Apr-2007 : Dispose some of the swt ressources,
 *               thanks to silent for pointing this out (HP);
 * 23-May-2007 : Removed resource leaks by adding a resource pool (CC);
 * 15-Jun-2007 : Fixed compile error for JDK 1.4 (DG);
 * 22-Oct-2007 : Implemented clipping (HP);
 * 22-Oct-2007 : Implemented some AlphaComposite support (HP);
 * 23-Oct-2007 : Added mechanism for storing RenderingHints (which are
 *               still ignored at this point) (DG);
 * 23-Oct-2007 : Implemented drawPolygon(), drawPolyline(), drawOval(),
 *               fillOval(), drawArc() and fillArc() (DG);
 * 27-Nov-2007 : Implemented a couple of drawImage() methods (DG);
 * 18-Nov-2008 : Check for GradientPaint in setPaint() method (DG);
 * 27-Feb-2009 : Implemented fillPolygon() - see bug 2583891 (DG);
 *
 */

package org.jfree.experimental.swt;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.RenderingHints.Key;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Resource;
import org.eclipse.swt.graphics.Transform;

/**
 * This is a class utility to draw Graphics2D stuff on a swt composite. It is
 * presently developed to use JFreeChart with the Standard Widget Toolkit but
 * may be of a wider use later.
 */
public class SWTGraphics2D extends Graphics2D {

	/** The swt graphic composite */
	private GC gc;

	/**
	 * The rendering hints. For now, these are not used, but at least the basic
	 * mechanism is present.
	 */
	private RenderingHints hints;

	/**
	 * A reference to the compositing rule to apply. This is necessary due to
	 * the poor compositing interface of the SWT toolkit.
	 */
	private java.awt.Composite composite;

	/** A HashMap to store the Swt color resources. */
	private Map<Integer, org.eclipse.swt.graphics.Color> colorsPool = new HashMap<Integer, org.eclipse.swt.graphics.Color>();

	/** A HashMap to store the Swt font resources. */
	private Map<Font, org.eclipse.swt.graphics.Font> fontsPool = new HashMap<Font, org.eclipse.swt.graphics.Font>();

	/** A HashMap to store the Swt transform resources. */
	private Map<AffineTransform, Transform> transformsPool = new HashMap<AffineTransform, Transform>();

	/** A List to store the Swt resources. */
	private List<Resource> resourcePool = new ArrayList<Resource>();

	/**
	 * Creates a new instance.
	 * 
	 * @param gc
	 *            the graphics context.
	 */
	public SWTGraphics2D(GC gc) {
		super();
		this.gc = gc;
		this.hints = new RenderingHints(null);
		this.composite = AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f);
	}

	/**
	 * Not implemented yet - see {@link Graphics#create()}.
	 * 
	 * @return <code>null</code>.
	 */
	public Graphics create() {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * Not implemented yet - see {@link Graphics2D#getDeviceConfiguration()}.
	 * 
	 * @return <code>null</code>.
	 */
	public GraphicsConfiguration getDeviceConfiguration() {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * Returns the current value for the specified hint key, or
	 * <code>null</code> if no value is set.
	 * 
	 * @param hintKey
	 *            the hint key (<code>null</code> permitted).
	 * 
	 * @return The hint value, or <code>null</code>.
	 * 
	 * @see #setRenderingHint(RenderingHints.Key, Object)
	 */
	public Object getRenderingHint(Key hintKey) {
		return this.hints.get(hintKey);
	}

	/**
	 * Sets the value for a rendering hint. For now, this graphics context
	 * ignores all hints.
	 * 
	 * @param hintKey
	 *            the key (<code>null</code> not permitted).
	 * @param hintValue
	 *            the value (must be compatible with the specified key).
	 * 
	 * @throws IllegalArgumentException
	 *             if <code>hintValue</code> is not compatible with the
	 *             <code>hintKey</code>.
	 * 
	 * @see #getRenderingHint(RenderingHints.Key)
	 */
	public void setRenderingHint(Key hintKey, Object hintValue) {
		this.hints.put(hintKey, hintValue);
	}

	/**
	 * Returns a copy of the hints collection for this graphics context.
	 * 
	 * @return A copy of the hints collection.
	 */
	public RenderingHints getRenderingHints() {
		return (RenderingHints) this.hints.clone();
	}

	/**
	 * Adds the hints in the specified map to the graphics context, replacing
	 * any existing hints. For now, this graphics context ignores all hints.
	 * 
	 * @param hints
	 *            the hints (<code>null</code> not permitted).
	 * 
	 * @see #setRenderingHints(Map)
	 */
	public void addRenderingHints(Map<?, ?> hints) {
		this.hints.putAll(hints);
	}

	/**
	 * Replaces the existing hints with those contained in the specified map.
	 * Note that, for now, this graphics context ignores all hints.
	 * 
	 * @param hints
	 *            the hints (<code>null</code> not permitted).
	 * 
	 * @see #addRenderingHints(Map)
	 */
	@SuppressWarnings("unchecked")
	public void setRenderingHints(@SuppressWarnings("rawtypes") Map hints) {
		if (hints == null) {
			throw new NullPointerException("Null 'hints' argument.");
		}
		this.hints = new RenderingHints(hints);
	}

	/**
	 * Returns the current paint for this graphics context.
	 * 
	 * @return The current paint.
	 * 
	 * @see #setPaint(Paint)
	 */
	public Paint getPaint() {
		// TODO: it might be a good idea to keep a reference to the color
		// specified in setPaint() or setColor(), rather than creating a
		// new object every time getPaint() is called.
		return SWTUtils.toAwtColor(this.gc.getForeground());
	}

	/**
	 * Sets the paint for this graphics context. For now, this graphics context
	 * only supports instances of {@link Color} or {@link GradientPaint} (in the
	 * latter case there is no real gradient support, the paint used is the
	 * <code>Color</code> returned by <code>getColor1()</code>).
	 * 
	 * @param paint
	 *            the paint (<code>null</code> not permitted).
	 * 
	 * @see #getPaint()
	 * @see #setColor(Color)
	 */
	public void setPaint(Paint paint) {
		if (paint instanceof Color) {
			setColor((Color) paint);
		} else if (paint instanceof GradientPaint) {
			GradientPaint gp = (GradientPaint) paint;
			setColor(gp.getColor1());
		} else {
			throw new RuntimeException("Can only handle 'Color' at present.");
		}
	}

	/**
	 * Returns the current color for this graphics context.
	 * 
	 * @return The current color.
	 * 
	 * @see #setColor(Color)
	 */
	public Color getColor() {
		// TODO: it might be a good idea to keep a reference to the color
		// specified in setPaint() or setColor(), rather than creating a
		// new object every time getPaint() is called.
		return SWTUtils.toAwtColor(this.gc.getForeground());
	}

	/**
	 * Sets the current color for this graphics context.
	 * 
	 * @param color
	 *            the color.
	 * 
	 * @see #getColor()
	 */
	public void setColor(Color color) {
		org.eclipse.swt.graphics.Color swtColor = getSwtColorFromPool(color);
		this.gc.setForeground(swtColor);
		// handle transparency and compositing.
		if (this.composite instanceof AlphaComposite) {
			AlphaComposite acomp = (AlphaComposite) this.composite;
			switch (acomp.getRule()) {
			case AlphaComposite.SRC_OVER:
				this.gc.setAlpha((int) (color.getAlpha() * acomp.getAlpha()));
				break;
			default:
				this.gc.setAlpha(color.getAlpha());
				break;
			}
		}
	}

	/**
	 * Sets the background colour.
	 * 
	 * @param color
	 *            the colour.
	 */
	public void setBackground(Color color) {
		org.eclipse.swt.graphics.Color swtColor = getSwtColorFromPool(color);
		this.gc.setBackground(swtColor);
	}

	/**
	 * Returns the background colour.
	 * 
	 * @return The background colour.
	 */
	public Color getBackground() {
		return SWTUtils.toAwtColor(this.gc.getBackground());
	}

	/**
	 * Not implemented - see {@link Graphics#setPaintMode()}.
	 */
	public void setPaintMode() {
		// TODO Auto-generated method stub
	}

	/**
	 * Not implemented - see {@link Graphics#setXORMode(Color)}.
	 * 
	 * @param color
	 *            the colour.
	 */
	public void setXORMode(Color color) {
		// TODO Auto-generated method stub
	}

	/**
	 * Returns the current composite.
	 * 
	 * @return The current composite.
	 * 
	 * @see #setComposite(Composite)
	 */
	public Composite getComposite() {
		return this.composite;
	}

	/**
	 * Sets the current composite. This implementation currently supports only
	 * the {@link AlphaComposite} class.
	 * 
	 * @param comp
	 *            the composite.
	 */
	public void setComposite(Composite comp) {
		this.composite = comp;
		if (comp instanceof AlphaComposite) {
			AlphaComposite acomp = (AlphaComposite) comp;
			int alpha = (int) (acomp.getAlpha() * 0xFF);
			this.gc.setAlpha(alpha);
		} else {
			System.out
					.println("warning, can only handle alpha composite at the moment.");
		}
	}

	/**
	 * Returns the current stroke for this graphics context.
	 * 
	 * @return The current stroke.
	 * 
	 * @see #setStroke(Stroke)
	 */
	public Stroke getStroke() {
		return new BasicStroke(this.gc.getLineWidth(), this.gc.getLineCap(),
				this.gc.getLineJoin());
	}

	/**
	 * Sets the stroke for this graphics context. For now, this implementation
	 * only recognises the {@link BasicStroke} class.
	 * 
	 * @param stroke
	 *            the stroke (<code>null</code> not permitted).
	 * 
	 * @see #getStroke()
	 */
	public void setStroke(Stroke stroke) {
		if (stroke instanceof BasicStroke) {
			BasicStroke bs = (BasicStroke) stroke;
			// linewidth
			this.gc.setLineWidth((int) bs.getLineWidth());

			// line join
			switch (bs.getLineJoin()) {
			case BasicStroke.JOIN_BEVEL:
				this.gc.setLineJoin(SWT.JOIN_BEVEL);
				break;
			case BasicStroke.JOIN_MITER:
				this.gc.setLineJoin(SWT.JOIN_MITER);
				break;
			case BasicStroke.JOIN_ROUND:
				this.gc.setLineJoin(SWT.JOIN_ROUND);
				break;
			}

			// line cap
			switch (bs.getEndCap()) {
			case BasicStroke.CAP_BUTT:
				this.gc.setLineCap(SWT.CAP_FLAT);
				break;
			case BasicStroke.CAP_ROUND:
				this.gc.setLineCap(SWT.CAP_ROUND);
				break;
			case BasicStroke.CAP_SQUARE:
				this.gc.setLineCap(SWT.CAP_SQUARE);
				break;
			}

			// set the line style to solid by default
			this.gc.setLineStyle(SWT.LINE_SOLID);

			// apply dash style if any
			float[] dashes = bs.getDashArray();
			if (dashes != null) {
				int[] swtDashes = new int[dashes.length];
				for (int i = 0; i < swtDashes.length; i++) {
					swtDashes[i] = (int) dashes[i];
				}
				this.gc.setLineDash(swtDashes);
			}
		} else {
			throw new RuntimeException(
					"Can only handle 'Basic Stroke' at present.");
		}
	}

	/**
	 * Applies the specified clip.
	 * 
	 * @param s
	 *            the shape for the clip.
	 */
	public void clip(Shape s) {
		Path path = toSwtPath(s);
		this.gc.setClipping(path);
		path.dispose();
	}

	/**
	 * Returns the clip bounds.
	 * 
	 * @return The clip bounds.
	 */
	public Rectangle getClipBounds() {
		org.eclipse.swt.graphics.Rectangle clip = this.gc.getClipping();
		return new Rectangle(clip.x, clip.y, clip.width, clip.height);
	}

	/**
	 * Sets the clipping to the intersection of the current clip region and the
	 * specified rectangle.
	 * 
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the width.
	 * @param height
	 *            the height.
	 */
	public void clipRect(int x, int y, int width, int height) {
		org.eclipse.swt.graphics.Rectangle clip = this.gc.getClipping();
		clip.intersects(x, y, width, height);
		this.gc.setClipping(clip);
	}

	/**
	 * Returns the current clip.
	 * 
	 * @return The current clip.
	 */
	public Shape getClip() {
		return SWTUtils.toAwtRectangle(this.gc.getClipping());
	}

	/**
	 * Sets the clip region.
	 * 
	 * @param clip
	 *            the clip.
	 */
	public void setClip(Shape clip) {
		if (clip == null) {
			return;
		}
		Path clipPath = toSwtPath(clip);
		this.gc.setClipping(clipPath);
		clipPath.dispose();
	}

	/**
	 * Sets the clip region to the specified rectangle.
	 * 
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the width.
	 * @param height
	 *            the height.
	 */
	public void setClip(int x, int y, int width, int height) {
		this.gc.setClipping(x, y, width, height);
	}

	/**
	 * Returns the current transform.
	 * 
	 * @return The current transform.
	 */
	public AffineTransform getTransform() {
		Transform swtTransform = new Transform(this.gc.getDevice());
		this.gc.getTransform(swtTransform);
		AffineTransform awtTransform = toAwtTransform(swtTransform);
		swtTransform.dispose();
		return awtTransform;
	}

	/**
	 * Sets the current transform.
	 * 
	 * @param t
	 *            the transform.
	 */
	public void setTransform(AffineTransform t) {
		Transform transform = getSwtTransformFromPool(t);
		this.gc.setTransform(transform);
	}

	/**
	 * Concatenates the specified transform to the existing transform.
	 * 
	 * @param t
	 *            the transform.
	 */
	public void transform(AffineTransform t) {
		Transform swtTransform = new Transform(this.gc.getDevice());
		this.gc.getTransform(swtTransform);
		swtTransform.multiply(getSwtTransformFromPool(t));
		this.gc.setTransform(swtTransform);
		swtTransform.dispose();
	}

	/**
	 * Applies a translation.
	 * 
	 * @param x
	 *            the translation along the x-axis.
	 * @param y
	 *            the translation along the y-axis.
	 */
	public void translate(int x, int y) {
		Transform swtTransform = new Transform(this.gc.getDevice());
		this.gc.getTransform(swtTransform);
		swtTransform.translate(x, y);
		this.gc.setTransform(swtTransform);
		swtTransform.dispose();
	}

	/**
	 * Applies a translation.
	 * 
	 * @param tx
	 *            the translation along the x-axis.
	 * @param ty
	 *            the translation along the y-axis.
	 */
	public void translate(double tx, double ty) {
		translate((int) tx, (int) ty);
	}

	/**
	 * Applies a rotation transform.
	 * 
	 * @param theta
	 *            the angle of rotation.
	 */
	public void rotate(double theta) {
		Transform swtTransform = new Transform(this.gc.getDevice());
		this.gc.getTransform(swtTransform);
		swtTransform.rotate((float) (theta * 180 / Math.PI));
		this.gc.setTransform(swtTransform);
		swtTransform.dispose();
	}

	/**
	 * Not implemented - see {@link Graphics2D#rotate(double, double, double)}.
	 * 
	 * @see java.awt.Graphics2D#rotate(double, double, double)
	 */
	public void rotate(double theta, double x, double y) {
		// TODO Auto-generated method stub
	}

	/**
	 * Applies a scale transform.
	 * 
	 * @param scaleX
	 *            the scale factor along the x-axis.
	 * @param scaleY
	 *            the scale factor along the y-axis.
	 */
	public void scale(double scaleX, double scaleY) {
		Transform swtTransform = new Transform(this.gc.getDevice());
		this.gc.getTransform(swtTransform);
		swtTransform.scale((float) scaleX, (float) scaleY);
		this.gc.setTransform(swtTransform);
		swtTransform.dispose();
	}

	/**
	 * Applies a shear transform.
	 * 
	 * @param shearX
	 *            the x-factor.
	 * @param shearY
	 *            the y-factor.
	 */
	public void shear(double shearX, double shearY) {
		Transform swtTransform = new Transform(this.gc.getDevice());
		this.gc.getTransform(swtTransform);
		Transform shear = new Transform(this.gc.getDevice(), 1f,
				(float) shearX, (float) shearY, 1f, 0, 0);
		swtTransform.multiply(shear);
		this.gc.setTransform(swtTransform);
		swtTransform.dispose();
	}

	/**
	 * Draws the outline of the specified shape using the current stroke and
	 * paint settings.
	 * 
	 * @param shape
	 *            the shape (<code>null</code> not permitted).
	 * 
	 * @see #getPaint()
	 * @see #getStroke()
	 * @see #fill(Shape)
	 */
	public void draw(Shape shape) {
		Path path = toSwtPath(shape);
		this.gc.drawPath(path);
		path.dispose();
	}

	/**
	 * Draws a line from (x1, y1) to (x2, y2) using the current stroke and paint
	 * settings.
	 * 
	 * @param x1
	 *            the x-coordinate for the starting point.
	 * @param y1
	 *            the y-coordinate for the starting point.
	 * @param x2
	 *            the x-coordinate for the ending point.
	 * @param y2
	 *            the y-coordinate for the ending point.
	 * 
	 * @see #draw(Shape)
	 */
	public void drawLine(int x1, int y1, int x2, int y2) {
		this.gc.drawLine(x1, y1, x2, y2);
	}

	/**
	 * Draws the outline of the polygon specified by the given points, using the
	 * current paint and stroke settings.
	 * 
	 * @param xPoints
	 *            the x-coordinates.
	 * @param yPoints
	 *            the y-coordinates.
	 * @param npoints
	 *            the number of points in the polygon.
	 * 
	 * @see #draw(Shape)
	 */
	public void drawPolygon(int[] xPoints, int[] yPoints, int npoints) {
		drawPolyline(xPoints, yPoints, npoints);
		if (npoints > 1) {
			this.gc.drawLine(xPoints[npoints - 1], yPoints[npoints - 1],
					xPoints[0], yPoints[0]);
		}
	}

	/**
	 * Draws a sequence of connected lines specified by the given points, using
	 * the current paint and stroke settings.
	 * 
	 * @param xPoints
	 *            the x-coordinates.
	 * @param yPoints
	 *            the y-coordinates.
	 * @param npoints
	 *            the number of points in the polygon.
	 * 
	 * @see #draw(Shape)
	 */
	public void drawPolyline(int[] xPoints, int[] yPoints, int npoints) {
		if (npoints > 1) {
			int x0 = xPoints[0];
			int y0 = yPoints[0];
			int x1 = 0, y1 = 0;
			for (int i = 1; i < npoints; i++) {
				x1 = xPoints[i];
				y1 = yPoints[i];
				this.gc.drawLine(x0, y0, x1, y1);
				x0 = x1;
				y0 = y1;
			}
		}
	}

	/**
	 * Draws an oval that fits within the specified rectangular region.
	 * 
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the frame width.
	 * @param height
	 *            the frame height.
	 * 
	 * @see #fillOval(int, int, int, int)
	 * @see #draw(Shape)
	 */
	public void drawOval(int x, int y, int width, int height) {
		this.gc.drawOval(x, y, width - 1, height - 1);
	}

	/**
	 * Draws an arc that is part of an ellipse that fits within the specified
	 * framing rectangle.
	 * 
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the frame width.
	 * @param height
	 *            the frame height.
	 * @param arcStart
	 *            the arc starting point, in degrees.
	 * @param arcAngle
	 *            the extent of the arc.
	 * 
	 * @see #fillArc(int, int, int, int, int, int)
	 */
	public void drawArc(int x, int y, int width, int height, int arcStart,
			int arcAngle) {
		this.gc.drawArc(x, y, width - 1, height - 1, arcStart, arcAngle);
	}

	/**
	 * Draws a rectangle with rounded corners that fits within the specified
	 * framing rectangle.
	 * 
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the frame width.
	 * @param height
	 *            the frame height.
	 * @param arcWidth
	 *            the width of the arc defining the roundedness of the
	 *            rectangle's corners.
	 * @param arcHeight
	 *            the height of the arc defining the roundedness of the
	 *            rectangle's corners.
	 * 
	 * @see #fillRoundRect(int, int, int, int, int, int)
	 */
	public void drawRoundRect(int x, int y, int width, int height,
			int arcWidth, int arcHeight) {
		this.gc.drawRoundRectangle(x, y, width - 1, height - 1, arcWidth,
				arcHeight);
	}

	/**
	 * Fills the specified shape using the current paint.
	 * 
	 * @param shape
	 *            the shape (<code>null</code> not permitted).
	 * 
	 * @see #getPaint()
	 * @see #draw(Shape)
	 */
	public void fill(Shape shape) {
		Path path = toSwtPath(shape);
		// Note that for consistency with the AWT implementation, it is
		// necessary to switch temporarily the foreground and background
		// colours
		switchColors();
		this.gc.fillPath(path);
		switchColors();
		path.dispose();
	}

	/**
	 * Fill a rectangle area on the swt graphic composite. The
	 * <code>fillRectangle</code> method of the <code>GC</code> class uses the
	 * background color so we must switch colors.
	 * 
	 * @see java.awt.Graphics#fillRect(int, int, int, int)
	 */
	public void fillRect(int x, int y, int width, int height) {
		this.switchColors();
		this.gc.fillRectangle(x, y, width, height);
		this.switchColors();
	}

	/**
	 * Fills the specified rectangle with the current background colour.
	 * 
	 * @param x
	 *            the x-coordinate for the rectangle.
	 * @param y
	 *            the y-coordinate for the rectangle.
	 * @param width
	 *            the width.
	 * @param height
	 *            the height.
	 * 
	 * @see #fillRect(int, int, int, int)
	 */
	public void clearRect(int x, int y, int width, int height) {
		Paint saved = getPaint();
		setPaint(getBackground());
		fillRect(x, y, width, height);
		setPaint(saved);
	}

	/**
	 * Fills the specified polygon.
	 * 
	 * @param xPoints
	 *            the x-coordinates.
	 * @param yPoints
	 *            the y-coordinates.
	 * @param npoints
	 *            the number of points.
	 */
	public void fillPolygon(int[] xPoints, int[] yPoints, int npoints) {
		int[] pointArray = new int[npoints * 2];
		for (int i = 0; i < npoints; i++) {
			pointArray[2 * i] = xPoints[i];
			pointArray[2 * i + 1] = yPoints[i];
		}
		switchColors();
		this.gc.fillPolygon(pointArray);
		switchColors();
	}

	/**
	 * Draws a rectangle with rounded corners that fits within the specified
	 * framing rectangle.
	 * 
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the frame width.
	 * @param height
	 *            the frame height.
	 * @param arcWidth
	 *            the width of the arc defining the roundedness of the
	 *            rectangle's corners.
	 * @param arcHeight
	 *            the height of the arc defining the roundedness of the
	 *            rectangle's corners.
	 * 
	 * @see #drawRoundRect(int, int, int, int, int, int)
	 */
	public void fillRoundRect(int x, int y, int width, int height,
			int arcWidth, int arcHeight) {
		switchColors();
		this.gc.fillRoundRectangle(x, y, width - 1, height - 1, arcWidth,
				arcHeight);
		switchColors();
	}

	/**
	 * Fills an oval that fits within the specified rectangular region.
	 * 
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the frame width.
	 * @param height
	 *            the frame height.
	 * 
	 * @see #drawOval(int, int, int, int)
	 * @see #fill(Shape)
	 */
	public void fillOval(int x, int y, int width, int height) {
		switchColors();
		this.gc.fillOval(x, y, width - 1, height - 1);
		switchColors();
	}

	/**
	 * Fills an arc that is part of an ellipse that fits within the specified
	 * framing rectangle.
	 * 
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the frame width.
	 * @param height
	 *            the frame height.
	 * @param arcStart
	 *            the arc starting point, in degrees.
	 * @param arcAngle
	 *            the extent of the arc.
	 * 
	 * @see #drawArc(int, int, int, int, int, int)
	 */
	public void fillArc(int x, int y, int width, int height, int arcStart,
			int arcAngle) {
		switchColors();
		this.gc.fillArc(x, y, width - 1, height - 1, arcStart, arcAngle);
		switchColors();
	}

	/**
	 * Returns the font in form of an awt font created with the parameters of
	 * the font of the swt graphic composite.
	 * 
	 * @see java.awt.Graphics#getFont()
	 */
	public Font getFont() {
		// retrieve the swt font description in an os indept way
		FontData[] fontData = this.gc.getFont().getFontData();
		// create a new awt font with the appropiate data
		return SWTUtils.toAwtFont(this.gc.getDevice(), fontData[0], true);
	}

	/**
	 * Set the font swt graphic composite from the specified awt font. Be
	 * careful that the newly created swt font must be disposed separately.
	 * 
	 * @see java.awt.Graphics#setFont(java.awt.Font)
	 */
	public void setFont(Font font) {
		org.eclipse.swt.graphics.Font swtFont = getSwtFontFromPool(font);
		this.gc.setFont(swtFont);
	}

	/**
	 * Returns the font metrics.
	 * 
	 * @param font
	 *            the font.
	 * 
	 * @return The font metrics.
	 */
	public FontMetrics getFontMetrics(Font font) {
		return SWTUtils.DUMMY_PANEL.getFontMetrics(font);
	}

	/**
	 * Returns the font render context.
	 * 
	 * @return The font render context.
	 */
	public FontRenderContext getFontRenderContext() {
		FontRenderContext fontRenderContext = new FontRenderContext(
				new AffineTransform(), true, true);
		return fontRenderContext;
	}

	/**
	 * Not implemented - see
	 * {@link Graphics2D#drawGlyphVector(GlyphVector, float, float)}.
	 */
	public void drawGlyphVector(GlyphVector g, float x, float y) {
		// TODO Auto-generated method stub

	}

	/**
	 * Draws a string on the receiver. note that to be consistent with the awt
	 * method, the y has to be modified with the ascent of the font.
	 * 
	 * @see java.awt.Graphics#drawString(java.lang.String, int, int)
	 */
	public void drawString(String text, int x, int y) {
		float fm = this.gc.getFontMetrics().getAscent();
		this.gc.drawString(text, x, (int) (y - fm), true);
	}

	/**
	 * Draws a string at the specified position.
	 * 
	 * @param text
	 *            the string.
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 */
	public void drawString(String text, float x, float y) {
		float fm = this.gc.getFontMetrics().getAscent();
		this.gc.drawString(text, (int) x, (int) (y - fm), true);
	}

	/**
	 * Draws a string at the specified position.
	 * 
	 * @param iterator
	 *            the string.
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 */
	public void drawString(AttributedCharacterIterator iterator, int x, int y) {
		// for now we simply want to extract the chars from the iterator
		// and call an unstyled text renderer
		StringBuffer sb = new StringBuffer();
		int numChars = iterator.getEndIndex() - iterator.getBeginIndex();
		char c = iterator.first();
		for (int i = 0; i < numChars; i++) {
			sb.append(c);
			c = iterator.next();
		}

		drawString(new String(sb), x, y);
	}

	/**
	 * Draws a string at the specified position.
	 * 
	 * @param iterator
	 *            the string.
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 */
	public void drawString(AttributedCharacterIterator iterator, float x,
			float y) {
		drawString(iterator, (int) x, (int) y);
	}

	/**
	 * Not implemented - see {@link Graphics2D#hit(Rectangle, Shape, boolean)}.
	 * 
	 * @return <code>false</code>.
	 */
	public boolean hit(Rectangle rect, Shape text, boolean onStroke) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * Not implemented - see
	 * {@link Graphics#copyArea(int, int, int, int, int, int)}.
	 */
	public void copyArea(int x, int y, int width, int height, int dx, int dy) {
		// TODO Auto-generated method stub
	}

	/**
	 * Not implemented - see
	 * {@link Graphics2D#drawImage(Image, AffineTransform, ImageObserver)}.
	 * 
	 * @param image
	 *            the image.
	 * @param xform
	 *            the transform.
	 * @param obs
	 *            an image observer.
	 * 
	 * @return A boolean.
	 */
	public boolean drawImage(Image image, AffineTransform xform,
			ImageObserver obs) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * Draws an image.
	 * 
	 * @param image
	 *            the image.
	 * @param op
	 *            the image operation.
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 */
	public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y) {
		org.eclipse.swt.graphics.Image im = new org.eclipse.swt.graphics.Image(
				this.gc.getDevice(), SWTUtils.convertToSWT(image));
		this.gc.drawImage(im, x, y);
		im.dispose();
	}

	/**
	 * Draws an SWT image with the top left corner of the image aligned to the
	 * point (x, y).
	 * 
	 * @param image
	 *            the image.
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 */
	public void drawImage(org.eclipse.swt.graphics.Image image, int x, int y) {
		this.gc.drawImage(image, x, y);
	}

	/**
	 * Not implemented - see
	 * {@link Graphics2D#drawRenderedImage(RenderedImage, AffineTransform)}.
	 * 
	 * @param image
	 *            the image.
	 * @param xform
	 *            the transform.
	 */
	public void drawRenderedImage(RenderedImage image, AffineTransform xform) {
		// TODO Auto-generated method stub
	}

	/**
	 * Not implemented - see
	 * {@link Graphics2D#drawRenderableImage(RenderableImage, AffineTransform)}.
	 * 
	 * @param image
	 *            the image.
	 * @param xform
	 *            the transform.
	 */
	public void drawRenderableImage(RenderableImage image, AffineTransform xform) {
		// TODO Auto-generated method stub

	}

	/**
	 * Draws an image with the top left corner aligned to the point (x, y).
	 * 
	 * @param image
	 *            the image.
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param observer
	 *            ignored here.
	 * 
	 * @return <code>true</code> if the image has been drawn.
	 */
	public boolean drawImage(Image image, int x, int y, ImageObserver observer) {
		ImageData data = SWTUtils.convertAWTImageToSWT(image);
		if (data == null) {
			return false;
		}
		org.eclipse.swt.graphics.Image im = new org.eclipse.swt.graphics.Image(
				this.gc.getDevice(), data);
		this.gc.drawImage(im, x, y);
		im.dispose();
		return true;
	}

	/**
	 * Draws an image with the top left corner aligned to the point (x, y), and
	 * scaled to the specified width and height.
	 * 
	 * @param image
	 *            the image.
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the width for the rendered image.
	 * @param height
	 *            the height for the rendered image.
	 * @param observer
	 *            ignored here.
	 * 
	 * @return <code>true</code> if the image has been drawn.
	 */
	public boolean drawImage(Image image, int x, int y, int width, int height,
			ImageObserver observer) {
		ImageData data = SWTUtils.convertAWTImageToSWT(image);
		if (data == null) {
			return false;
		}
		org.eclipse.swt.graphics.Image im = new org.eclipse.swt.graphics.Image(
				this.gc.getDevice(), data);
		org.eclipse.swt.graphics.Rectangle bounds = im.getBounds();
		this.gc.drawImage(im, 0, 0, bounds.width, bounds.height, x, y, width,
				height);
		im.dispose();
		return true;
	}

	/**
	 * Draws an image.
	 * 
	 * @param image
	 *            (<code>null</code> not permitted).
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param bgcolor
	 *            the background color.
	 * @param observer
	 *            an image observer.
	 * 
	 * @return A boolean.
	 */
	public boolean drawImage(Image image, int x, int y, Color bgcolor,
			ImageObserver observer) {
		if (image == null) {
			throw new IllegalArgumentException("Null 'image' argument.");
		}
		int w = image.getWidth(null);
		int h = image.getHeight(null);
		if (w == -1 || h == -1) {
			return false;
		}
		Paint savedPaint = getPaint();
		fill(new Rectangle2D.Double(x, y, w, h));
		setPaint(savedPaint);
		return drawImage(image, x, y, observer);
	}

	/**
	 * Draws an image.
	 * 
	 * @param image
	 *            the image (<code>null</code> not permitted).
	 * @param x
	 *            the x-coordinate.
	 * @param y
	 *            the y-coordinate.
	 * @param width
	 *            the width.
	 * @param height
	 *            the height.
	 * @param bgcolor
	 *            the background colour.
	 * @param observer
	 *            an image observer.
	 * 
	 * @return A boolean.
	 */
	public boolean drawImage(Image image, int x, int y, int width, int height,
			Color bgcolor, ImageObserver observer) {
		if (image == null) {
			throw new IllegalArgumentException("Null 'image' argument.");
		}
		int w = image.getWidth(null);
		int h = image.getHeight(null);
		if (w == -1 || h == -1) {
			return false;
		}
		Paint savedPaint = getPaint();
		fill(new Rectangle2D.Double(x, y, w, h));
		setPaint(savedPaint);
		return drawImage(image, x, y, width, height, observer);
	}

	/**
	 * Not implemented - see
	 * {@link Graphics#drawImage(Image, int, int, int, int, int, int, int, int, ImageObserver)}
	 * .
	 * 
	 * @param image
	 *            the image.
	 * @param dx1
	 * @param dy1
	 * @param dx2
	 * @param dy2
	 * @param sx1
	 * @param sy1
	 * @param sx2
	 * @param sy2
	 * @param observer
	 */
	public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
			int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * Not implemented - see
	 * {@link Graphics#drawImage(Image, int, int, int, int, int, int, int, int, Color, ImageObserver)}
	 * .
	 * 
	 * @param image
	 *            the image.
	 * @param dx1
	 * @param dy1
	 * @param dx2
	 * @param dy2
	 * @param sx1
	 * @param sy1
	 * @param sx2
	 * @param sy2
	 * @param bgcolor
	 * @param observer
	 */
	public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
			int sx1, int sy1, int sx2, int sy2, Color bgcolor,
			ImageObserver observer) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * Releases resources held by this instance (but note that the caller must
	 * dispose of the 'GC' passed to the constructor).
	 * 
	 * @see java.awt.Graphics#dispose()
	 */
	public void dispose() {
		// we dispose resources we own but user must dispose gc
		disposeResourcePool();
	}

	/**
	 * Add given swt resource to the resource pool. All resources added to the
	 * resource pool will be disposed when {@link #dispose()} is called.
	 * 
	 * @param resource
	 *            the resource to add to the pool.
	 * @return the swt <code>Resource</code> just added.
	 */
	private Resource addToResourcePool(Resource resource) {
		this.resourcePool.add(resource);
		return resource;
	}

	/**
	 * Dispose the resource pool.
	 */
	private void disposeResourcePool() {
		for (Iterator<Resource> it = this.resourcePool.iterator(); it.hasNext();) {
			Resource resource = it.next();
			resource.dispose();
		}
		this.fontsPool.clear();
		this.colorsPool.clear();
		this.transformsPool.clear();
		this.resourcePool.clear();
	}

	/**
	 * Internal method to convert a AWT font object into a SWT font resource. If
	 * a corresponding SWT font instance is already in the pool, it will be used
	 * instead of creating a new one. This is used in {@link #setFont()} for
	 * instance.
	 * 
	 * @param font
	 *            The AWT font to convert.
	 * @return The SWT font instance.
	 */
	private org.eclipse.swt.graphics.Font getSwtFontFromPool(Font font) {
		org.eclipse.swt.graphics.Font swtFont = this.fontsPool.get(font);
		if (swtFont == null) {
			swtFont = new org.eclipse.swt.graphics.Font(this.gc.getDevice(),
					SWTUtils.toSwtFontData(this.gc.getDevice(), font, true));
			addToResourcePool(swtFont);
			this.fontsPool.put(font, swtFont);
		}
		return swtFont;
	}

	/**
	 * Internal method to convert a AWT color object into a SWT color resource.
	 * If a corresponding SWT color instance is already in the pool, it will be
	 * used instead of creating a new one. This is used in {@link #setColor()}
	 * for instance.
	 * 
	 * @param awtColor
	 *            The AWT color to convert.
	 * @return A SWT color instance.
	 */
	private org.eclipse.swt.graphics.Color getSwtColorFromPool(Color awtColor) {
		org.eclipse.swt.graphics.Color swtColor =
		// we can't use the following valueOf() method, because it
		// won't compile with JDK1.4
		// this.colorsPool.get(Integer.valueOf(awtColor.getRGB()));
		this.colorsPool.get(new Integer(awtColor.getRGB()));
		if (swtColor == null) {
			swtColor = SWTUtils.toSwtColor(this.gc.getDevice(), awtColor);
			addToResourcePool(swtColor);
			// see comment above
			// this.colorsPool.put(Integer.valueOf(awtColor.getRGB()),
			// swtColor);
			this.colorsPool.put(new Integer(awtColor.getRGB()), swtColor);
		}
		return swtColor;
	}

	/**
	 * Internal method to convert a AWT transform object into a SWT transform
	 * resource. If a corresponding SWT transform instance is already in the
	 * pool, it will be used instead of creating a new one. This is used in
	 * {@link #setTransform()} for instance.
	 * 
	 * @param awtTransform
	 *            The AWT transform to convert.
	 * @return A SWT transform instance.
	 */
	private Transform getSwtTransformFromPool(AffineTransform awtTransform) {
		Transform t = this.transformsPool.get(awtTransform);
		if (t == null) {
			t = new Transform(this.gc.getDevice());
			double[] matrix = new double[6];
			awtTransform.getMatrix(matrix);
			t.setElements((float) matrix[0], (float) matrix[1],
					(float) matrix[2], (float) matrix[3], (float) matrix[4],
					(float) matrix[5]);
			addToResourcePool(t);
			this.transformsPool.put(awtTransform, t);
		}
		return t;
	}

	/**
	 * Perform a switch between foreground and background color of gc. This is
	 * needed for consistency with the awt behaviour, and is required notably
	 * for the filling methods.
	 */
	private void switchColors() {
		org.eclipse.swt.graphics.Color bg = this.gc.getBackground();
		org.eclipse.swt.graphics.Color fg = this.gc.getForeground();
		this.gc.setBackground(fg);
		this.gc.setForeground(bg);
	}

	/**
	 * Converts an AWT <code>Shape</code> into a SWT <code>Path</code>.
	 * 
	 * @param shape
	 *            the shape (<code>null</code> not permitted).
	 * 
	 * @return The path.
	 */
	private Path toSwtPath(Shape shape) {
		int type;
		float[] coords = new float[6];
		Path path = new Path(this.gc.getDevice());
		PathIterator pit = shape.getPathIterator(null);
		while (!pit.isDone()) {
			type = pit.currentSegment(coords);
			switch (type) {
			case (PathIterator.SEG_MOVETO):
				path.moveTo(coords[0], coords[1]);
				break;
			case (PathIterator.SEG_LINETO):
				path.lineTo(coords[0], coords[1]);
				break;
			case (PathIterator.SEG_QUADTO):
				path.quadTo(coords[0], coords[1], coords[2], coords[3]);
				break;
			case (PathIterator.SEG_CUBICTO):
				path.cubicTo(coords[0], coords[1], coords[2], coords[3],
						coords[4], coords[5]);
				break;
			case (PathIterator.SEG_CLOSE):
				path.close();
				break;
			default:
				break;
			}
			pit.next();
		}
		return path;
	}

	/**
	 * Converts an SWT transform into the equivalent AWT transform.
	 * 
	 * @param swtTransform
	 *            the SWT transform.
	 * 
	 * @return The AWT transform.
	 */
	private AffineTransform toAwtTransform(Transform swtTransform) {
		float[] elements = new float[6];
		swtTransform.getElements(elements);
		AffineTransform awtTransform = new AffineTransform(elements);
		return awtTransform;
	}

}
